# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: inkyu

import datetime
import logging
import os

from coin.exchange.base.kr_rest.product import NativeProduct
from coin.exchange.base.kr_rest.futures_product import NativeFuturesProduct

from coin.proto.coin_feed_subscriber_pb2 import FeedSubscriberConfig

from coin.strategy.mm.subscription import SubscriptionRequest
from coin.strategy.mm.bitfinex.feed import BitfinexV2FeedSubsystem
from coin.strategy.mm.binance.feed import (
    BinanceFeedSubsystem,
    Binance1sFeedSubsystem,
    BinanceSnapshotL1RealtimeFeedSubsystem,
)
from coin.strategy.mm.binance.feed_snapshot import (
    BinanceSnapshotFeedSubsystem,
    BinanceSnapshot1sFeedSubsystem,
)
from coin.strategy.mm.binance_futures_swap.feed import (
    BinanceSwapFuturesFeedSubsystem,
    BinanceSwapFuturesSnapshotL1RealtimeFeedSubsystem,
)
from coin.strategy.mm.bitmex.feed import BitmexFeedSubsystem
from coin.strategy.mm.okex.feed import (OkexFeedSubsystem, OkexV3FeedSubsystem)
from coin.strategy.mm.huobi.feed import HuobiFeedSubsystem
from coin.strategy.mm.huobi_futures.feed import HuobiFuturesFeedSubsystem
from coin.strategy.mm.okex_futures.feed import (
    OkexFuturesFeedSubsystem,
    OkexFuturesV3FeedSubsystem,
    OkexFuturesV3RealtimeFeedSubsystem,
)
from coin.strategy.mm.bitflyer_v1.feed import BitflyerFeedSubsystem
from coin.strategy.mm.bitflyer_v1.feed_arb import BitflyerFeedArbSubsystem
from coin.strategy.mm.bithumb.feed import (
    BithumbFeedSubsystem, BithumbV2FeedSubsystem, BithumbV3FeedSubsystem)
from coin.strategy.mm.upbit.feed import UpbitFeedSubsystem
from coin.strategy.mm.kraken.feed import (KrakenRestFeedSubsystem, KrakenWsFeedSubsystem)
from coin.strategy.mm.kraken_futures.feed import KrakenFuturesFeedSubsystem
from coin.strategy.mm.quoinex_v2.feed import (
    QuoinexFeedSubsystem,
    QuoinexSingleChannelFeedSubsystem,
)
from coin.strategy.mm.hitbtc_v2.feed import HitbtcFeedSubsystem
from coin.strategy.mm.bequant_v2.feed import BequantFeedSubsystem
from coin.strategy.mm.okex_futures_swap.feed import OkexFuturesSwapFeedSubsystem
from coin.strategy.mm.gdax.feed import GdaxFeedSubsystem
from coin.strategy.mm.gdax.feed_arb import GdaxFeedArbSubsystem
from coin.strategy.mm.huobi_futures.feed_arb import HuobiFuturesFeedArbSubsystem
from coin.strategy.mm.bitmex.feed_arb import BitmexFeedArbSubsystem
from coin.strategy.mm.bitstamp_v2.feed import BitstampFeedSubsystem
from coin.strategy.mm.korbit_v1.feed import KorbitWsFeedSubsystem as KorbitFeedSubsystem
from coin.strategy.mm.coinone_v2.feed import CoinoneWsFeedSubsystem as CoinoneFeedSubsystem
from coin.strategy.mm.deribit_v2.feed import DeribitFeedSubsystem
from coin.strategy.mm.gopax.feed import GopaxFeedSubsystem
from coin.strategy.mm.bybit.feed import BybitFeedSubsystem
from coin.strategy.mm.uniswap_v3.feed import UniswapV3FeedSubsystem
from coin.strategy.mm.uniswap_v3_arbitrum.feed import UniswapV3ArbitrumFeedSubsystem
from coin.strategy.mm.uniswap_v2.feed import UniswapV2FeedSubsystem
from coin.strategy.mm.okex_futures.feed_arb import (OkexFuturesFeedArbSubsystem,
                                                    OkexFuturesV3FeedArbSubsystem)
from coin.strategy.mm.huobi.feed_arb import HuobiFeedArbSubsystem


def _get_bithumb_feed_subsystem(current_datetime):
  if os.getenv('USE_BITHUMB_V2_FEED'):
    logging.info('USE_BITHUMB_V2_FEED is set')
    return BithumbV2FeedSubsystem

  if os.getenv('USE_BITHUMB_V3_FEED'):
    logging.info('USE_BITHUMB_V3_FEED is set')
    return BithumbV3FeedSubsystem

  if current_datetime >= datetime.datetime.strptime('20200927', '%Y%m%d'):
    return BithumbV3FeedSubsystem
  # On 20200628, switch back to Bithumb rev-eng feed.
  # Official feed blocked by 502 error all the time.
  elif current_datetime >= datetime.datetime.strptime('20200628', '%Y%m%d'):
    return BithumbV2FeedSubsystem
  # On 20200618, Bithumb rev-eng feed stopped working with 403/502 error.
  elif current_datetime >= datetime.datetime.strptime('20200618', '%Y%m%d'):
    return BithumbV3FeedSubsystem
  # On 20190819, Bithumb websocket protocol changed.
  # And we introduce v2 afterwards.
  elif current_datetime >= datetime.datetime.strptime('20190819', '%Y%m%d'):
    return BithumbV2FeedSubsystem
  else:
    return BithumbFeedSubsystem


def get_feed_subsystem_cls(sub_key, *, fs_config=None, current_datetime=None):
  assert type(sub_key) == SubscriptionRequest, type(sub_key)
  current_datetime = current_datetime or datetime.datetime.now()

  bitflyer_feed_subsystem = BitflyerFeedSubsystem
  huobi_futures_feed_subsystem = HuobiFuturesFeedSubsystem
  huobi_feed_subsystem = HuobiFeedSubsystem
  gdax_feed_subsystem = GdaxFeedSubsystem
  if fs_config is not None:
    assert type(fs_config) == FeedSubscriberConfig, type(fs_config)
    if fs_config.use_feed_arbitration:
      bitmex_feed_subsystem = BitmexFeedArbSubsystem
      okex_futures_feed_subsystem = OkexFuturesFeedArbSubsystem
      okex_futures_v3_feed_subsystem = OkexFuturesV3FeedArbSubsystem
      bitflyer_feed_subsystem = BitflyerFeedArbSubsystem
      huobi_futures_feed_subsystem = HuobiFuturesFeedArbSubsystem
      huobi_feed_subsystem = HuobiFeedArbSubsystem
      gdax_feed_subsystem = GdaxFeedArbSubsystem
    else:
      bitmex_feed_subsystem = BitmexFeedSubsystem
      okex_futures_feed_subsystem = OkexFuturesFeedSubsystem
      okex_futures_v3_feed_subsystem = OkexFuturesV3FeedSubsystem
  else:
    bitmex_feed_subsystem = BitmexFeedSubsystem
    okex_futures_feed_subsystem = OkexFuturesFeedArbSubsystem
    okex_futures_v3_feed_subsystem = OkexFuturesV3FeedArbSubsystem

  feed_subsystem_map = {
      SubscriptionRequest('Spot', 'Bitfinex', None):
          BitfinexV2FeedSubsystem,
      SubscriptionRequest('Spot', 'Binance', None):
          BinanceFeedSubsystem,
      SubscriptionRequest('Spot', 'Binance', 'snapshot'):
          BinanceSnapshotFeedSubsystem,
      SubscriptionRequest('Spot', 'Binance', 'l1_realtime'):
          BinanceSnapshotL1RealtimeFeedSubsystem,
      SubscriptionRequest('Spot', 'Binance', 'update_1s'):
          Binance1sFeedSubsystem,
      SubscriptionRequest('Spot', 'Binance', 'snapshot_1s'):
          BinanceSnapshot1sFeedSubsystem,
      SubscriptionRequest('Spot', 'Bithumb', None):
          _get_bithumb_feed_subsystem(current_datetime),
      SubscriptionRequest('Spot', 'Upbit', None):
          UpbitFeedSubsystem,
      SubscriptionRequest('Spot', 'Okex', None):
          OkexV3FeedSubsystem,
      SubscriptionRequest('Spot', 'Okex', 'v1'):
          OkexFeedSubsystem,
      SubscriptionRequest('Spot', 'Okex', 'v3'):
          OkexV3FeedSubsystem,
      SubscriptionRequest('Spot', 'Huobi', None):
          huobi_feed_subsystem,
      SubscriptionRequest('Futures', 'Huobi', None):
          huobi_futures_feed_subsystem,
      SubscriptionRequest('Spot', 'Kraken', 'rest'):
          KrakenRestFeedSubsystem,
      SubscriptionRequest('Spot', 'Kraken', None):
          KrakenWsFeedSubsystem,
      SubscriptionRequest('Futures', 'Kraken', None):
          KrakenFuturesFeedSubsystem,
      SubscriptionRequest('Spot', 'Bitflyer', None):
          BitflyerFeedSubsystem,
      SubscriptionRequest('Futures', 'Bitmex', None):
          bitmex_feed_subsystem,
      SubscriptionRequest('Futures', 'Okex', None):
          okex_futures_v3_feed_subsystem,
      SubscriptionRequest('Futures', 'Okex', 'v1'):
          okex_futures_feed_subsystem,
      SubscriptionRequest('Futures', 'Okex', 'v3'):
          okex_futures_v3_feed_subsystem,
      SubscriptionRequest('Futures', 'Okex', 'v3_realtime'):
          OkexFuturesV3RealtimeFeedSubsystem,
      SubscriptionRequest('Futures', 'Okex', 'v3_swap'):
          OkexFuturesSwapFeedSubsystem,
      SubscriptionRequest('Futures', 'Bitflyer', None):
          bitflyer_feed_subsystem,
      SubscriptionRequest('Spot', 'Quoinex', None):
          QuoinexFeedSubsystem,
      SubscriptionRequest('Spot', 'Quoinex', 'single_channel'):
          QuoinexSingleChannelFeedSubsystem,
      SubscriptionRequest('Spot', 'Hitbtc', None):
          HitbtcFeedSubsystem,
      SubscriptionRequest('Spot', 'Bequant', None):
          BequantFeedSubsystem,
      SubscriptionRequest('Spot', 'Gdax', None):
          gdax_feed_subsystem,
      SubscriptionRequest('Spot', 'Bitstamp', None):
          BitstampFeedSubsystem,
      SubscriptionRequest('Spot', 'Korbit', None):
          KorbitFeedSubsystem,
      SubscriptionRequest('Spot', 'Coinone', None):
          CoinoneFeedSubsystem,
      SubscriptionRequest('Spot', 'Gopax', None):
          GopaxFeedSubsystem,
      SubscriptionRequest('Futures', 'Deribit', None):
          DeribitFeedSubsystem,
      SubscriptionRequest('Futures', 'Bybit', None):
          BybitFeedSubsystem,
      SubscriptionRequest('Spot', 'Uniswap', 'v3'):
        UniswapV3FeedSubsystem,
      SubscriptionRequest('Spot', 'Uniswap', 'v3-arbitrum'):
        UniswapV3ArbitrumFeedSubsystem,
      SubscriptionRequest('Spot', 'Uniswap', 'v2'):
        UniswapV2FeedSubsystem,
      SubscriptionRequest('Futures', 'Binance', None):
          BinanceSwapFuturesFeedSubsystem,
      SubscriptionRequest('Futures', 'Binance', 'l1_realtime'):
          BinanceSwapFuturesSnapshotL1RealtimeFeedSubsystem,
  }
  if sub_key in feed_subsystem_map:
    return feed_subsystem_map[sub_key]
  else:
    raise RuntimeError("not supported", sub_key)


def create_feed_sub_instance(sub_key,
                             products,
                             on_book_init_callback,
                             worker_id=1,
                             logger=None,
                             feed_checker_config=None):
  assert type(products) == list, products
  assert all((
      issubclass(product, (NativeProduct, NativeFuturesProduct)) for product in products)), products
  feed_subsystem_cls = get_feed_subsystem_cls(sub_key)
  instance = feed_subsystem_cls(products,
                                on_book_init_callback=on_book_init_callback,
                                worker_id=worker_id,
                                logger=logger,
                                feed_checker_config=feed_checker_config)
  return instance
